use derive_where::derive_where;
use indoc::indoc;
use serde::{Deserialize, Serialize};
use tracing::debug;

use crate::{flavor::MoveFlavor, schema::LockfileDependencyInfo};

use super::{
    EnvironmentID, EnvironmentName, PublishAddresses, RenderToml,
    toml_format::{expand_toml, flatten_toml},
};

/// The schema for a `Pub.<env>.toml` file. Local publication files differ from normal
/// `Published.toml` files because they store publication information for all of the package
/// dependencies, rather than just the root. They are also environment specific
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "", rename_all = "kebab-case")]
#[derive_where(Default, Clone)]
pub struct ParsedEphemeralPubs<F: MoveFlavor> {
    pub build_env: EnvironmentName,
    pub chain_id: EnvironmentID,

    #[serde(default)]
    pub published: Vec<LocalPub<F>>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "", rename_all = "kebab-case")]
#[derive_where(Clone)]
pub struct LocalPub<F: MoveFlavor> {
    pub source: LockfileDependencyInfo,

    #[serde(flatten)]
    pub addresses: PublishAddresses,

    pub version: u64,

    #[serde(flatten)]
    pub metadata: F::PublishedMetadata,
}

impl<F: MoveFlavor> RenderToml for ParsedEphemeralPubs<F> {
    /// Pretty-print `self` as TOML
    fn render_as_toml(&self) -> String {
        let mut toml = toml_edit::ser::to_document(self).expect("toml serialization succeeds");
        expand_toml(&mut toml);

        debug!("formatting\n{toml}");

        for entry in toml["published"]
            .as_array_of_tables_mut()
            .unwrap()
            .iter_mut()
        {
            debug!("entry: {entry:#?}");
            flatten_toml(&mut entry["source"]);
        }

        toml.decor_mut().set_prefix(indoc!(
            r#"
            # generated by Move
            # this file contains metadata from ephemeral publications
            # this file should not be committed to source control

            "#
        ));

        toml.to_string()
    }
}

#[cfg(test)]
mod tests {
    use indoc::indoc;
    use pretty_assertions::assert_eq;
    use test_log::test;

    use crate::{flavor::Vanilla, schema::RenderToml};

    use super::ParsedEphemeralPubs;

    /// Parsing and then rendering a local publication file produces the original input
    #[test]
    fn parse_render_publocal() {
        let original = indoc!(
            r###"
            # generated by Move
            # this file contains metadata from ephemeral publications
            # this file should not be committed to source control

            build-env = "mainnet"
            chain-id = "localnet chain ID"

            [[published]]
            source = { local = "../foo0" }
            published-at = "0x0000000000000000000000000000000000000000000000000000000000001234"
            original-id = "0x0000000000000000000000000000000000000000000000000000000000005678"
            version = 0
            build-config = { edition = "2024", flavor = "vanilla" }

            [[published]]
            source = { local = "../foo1" }
            published-at = "0x0000000000000000000000000000000000000000000000000000000000101234"
            original-id = "0x0000000000000000000000000000000000000000000000000000000000105678"
            version = 1
            build-config = { edition = "2024", flavor = "vanilla" }

            [[published]]
            source = { root = true }
            published-at = "0x000000000000000000000000000000000000000000000000000000000000cccc"
            original-id = "0x000000000000000000000000000000000000000000000000000000000000cc00"
            version = 2
            build-config = { edition = "2024", flavor = "vanilla" }
            "###
        );

        let parsed: ParsedEphemeralPubs<Vanilla> = toml_edit::de::from_str(original).unwrap();
        let rendered = parsed.render_as_toml();
        assert_eq!(rendered, original);
    }
}
